package com.ken.common.mysql.plugins;

import com.ken.common.mysql.page.KenPage;
import com.ken.common.mysql.page.Page;
import com.ken.common.mysql.utils.MybatisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 分页插件
 */
@Intercepts(
        @Signature(
                type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class}
        )
)
@Slf4j
public class PagePlugin implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //获得StatementHandler对象
        StatementHandler statementHandler = MybatisUtils.getNoProxyTarget(invocation.getTarget());
        //获得当前执行的sql
        BoundSql boundSql = statementHandler.getBoundSql();
        //获取sql前做一定的相关处理
        String sql = boundSql.getSql().toLowerCase().trim().replaceAll("\\s+", " ");

        //判断是否分页
        if (!sql.startsWith("select")) {
            //说明当前非查询语句，则无需分页
            //直接放行
            return invocation.proceed();
        }

        //获得分页参数
        Page page = KenPage.getPage();
        if (page == null || !page.isEnable()) {
            //说明当前没有设置分页参数，则无需分页
            return invocation.proceed();
        }

        //说明当前sql是需要分页的
        log.debug("[Page] - page sql - {}", sql);

        //构建一个查询分页总条数的sql
        Integer count = totalSql(invocation, statementHandler, sql);
        log.debug("[Page] - count - {}", count);

        //校验page对象
        if (page.getPageNum() == null || page.getPageNum() < 1) page.setPageNum(1);
        if (page.getPageSize() == null || page.getPageSize() < 1) page.setPageSize(10);

        //设置Page对象
        page.setPageCount(count);//设置总条数
        page.setPageTotal(page.getPageCount() % page.getPageSize() == 0 ?
                page.getPageCount() / page.getPageSize() :
                page.getPageCount() / page.getPageSize() + 1);//设置总页码

        //校验page对象
        if (page.getPageNum() > page.getPageTotal()) page.setPageNum(page.getPageTotal());
        log.debug("[Page] - page Object - {}", page);

        //开始进行SQL分页
        sql += " limit " + ((page.getPageNum() - 1) * page.getPageSize()) + "," + page.getPageSize();
        log.debug("[Page] - exec page sql - {}", sql);

        //MyBatis提供的一个工具类（使用反射修改BoundSql对象中的sql语句）
        MetaObject metaObject = SystemMetaObject.forObject(boundSql);
        metaObject.setValue("sql", sql);

        //放行
        return invocation.proceed();
    }

    /**
     * 执行查询总数量的sql
     * select xxxxxx from xxxxx where id = ? and age = ? order by xxxx,xxxx,xxxx
     * select count(*) from xxxxx where id = ? and age = ?
     *
     * select a,b,c,(select * from B where xxx) from table where id = (select xxx from C)
     *
     * @return
     */
    private Integer totalSql(Invocation invocation, StatementHandler statementHandler, String sql) throws SQLException {

        //判断是否存在排序的相关内容，如果存在则截取掉
        int orderByIndex;
        if((orderByIndex = sql.lastIndexOf("order by")) != -1) {
            sql = sql.substring(0, orderByIndex);
        }

        //找到sql语句中from的位置
        int fromIndex = MybatisUtils.getFromIndex(0, sql);
        //截取sql
        String totalSql = "select count(*) as total " + sql.substring(fromIndex);
        log.debug("[Page] - total sql - {}", totalSql);

        //执行查询总条数的sql
        //获得方法的参数
        Connection connection = (Connection) invocation.getArgs()[0];
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            //创建预编译的会话对象
            ps = connection.prepareStatement(totalSql);
            //设置sql参数
            statementHandler.parameterize(ps);
            //执行sql
            resultSet = ps.executeQuery();
            //获取resultset中的结果
            if (resultSet.next()) {
                return resultSet.getInt("total");
            }
        } catch (SQLException throwables) {
            throw throwables;
        } finally {
            if (resultSet != null) {
                resultSet.close();
            }
            if (ps != null) {
                ps.close();
            }
        }
        return -1;
    }
}
